<#include "license.ftl">
<@license/>
<#assign object = doc.object>
package ${object.@package}.service.base;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException;
<#assign stream = "false">
<#list doc["/object/attributes/*"] as att>
    <#if att?node_name == "html">
        <#assign stream = "true">
    <#elseif att?node_name == "string" && att.@maxlength[0]?number &gt; 65000>
        <#assign stream = "true">
    </#if>
</#list>
<#if stream == "true">
import java.io.IOException;
import java.sql.PreparedStatement;
</#if>
import java.sql.ResultSet;
import java.sql.SQLException;
<#if stream == "true">
import java.sql.Timestamp;
</#if>
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import redora.api.Persistable;
import redora.api.fetch.*;
import redora.exceptions.*;
import redora.service.*;
import ${object.@package}.model.*;
import ${object.@package}.model.fields.*;
import ${object.@package}.service.*;

<#list doc["/object/attributes/enum[@scope='global']"] as att>
import ${object.@package}.model.enums.${att.@class};
</#list>
<#if object.trashcan == "true">
import redora.configuration.rdo.service.TrashService;
</#if>

import static ${object.@package}.sql.${object.@name}SQL.*;
import static ${object.@package}.businessrules.${object.@name}BusinessRules.check;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.SEVERE;
import static redora.db.SQLParameter.*;

/**
* You can work with the pojo's using this service. For example if you create command line J2SE functionality
* or server side logic.<br>
* If you want to use your data in a web application, you also might want to look at {@link ${object.@name}ServiceJSON}
* and {@link ${object.@package}.service.http.${object.@name}Servlet}.<br>
* Whenever business rule logic needs to be checked, it will go through here. For example a web user wants to persist
* some data. He will post this for example as JSON to {@link ${object.@package}.service.http.${object.@name}Servlet}
* which in turn invokes {@link ${object.@name}ServiceJSON} which will serialize the JSON stream into a pojo
* and then will invoke the persist method of this service.
* @author Redora (www.redora.net)
*/
public class ${object.@name}ServiceBase extends ServiceBase {

    static final transient Logger l = Logger.getLogger("${object.@package}.service.${object.@name}Service");

    /**
    * Don't use this directly but use the ServiceFactory instead:
    * <code>
    * ${object.@name}Service() myService = ServiceFactory.${object.@name?uncap_first}();
    * ... do stuff
    * ServiceFactory.close(myService);
    * </code>
    * Initializes ${object.@name}Service.
    * @see ${object.@package}.service.ServiceFactory
    */

    protected ${object.@name}ServiceBase() throws ConnectException {
        super("${object.schema[0]!"default"}");
    }

    /**
    * Chains this service object to another service object. The database connection
    * and transaction is managed by the other service.
    * @param chain (Mandatory) Service you want to make this service dependent on.
    */
    protected ${object.@name}ServiceBase(@NotNull ServiceBase chain) {
        super(chain, "${object.schema[0]!"default"}");
        inTransaction = true; //Let the calling service handle the transaction
    }

    /**
    * Simple finder to get ${object.@name} by it's id.
    * @param id (Mandatory) The primary key.
    * @param scope (Mandatory) The allowed fetch scope: Scope.Table, Scope.Form, Scope.List.
    * @return The desired ${object.@name}
    * @throws ObjectNotFoundException When there is no object in the DB with this Id.
    */
    @NotNull
    public ${object.@name} findById(@NotNull Long id, @NotNull Scope scope)
            throws QueryException, CopyException, ObjectNotFoundException {
        ${object.@name} retVal = null;
        String SQL;
        switch (scope) {
            case Table:
                SQL = FIND_BY_ID_TABLE;
                break;
            case Form:
                SQL = FIND_BY_ID_FORM;
                break;
            case List:
                SQL = FIND_BY_ID_LIST;
                break;
            default:
                throw new IllegalArgumentException("Illegal scope for this finder: " + scope);
        }
        SQL = prepare(SQL, id);

        ResultSet rs = null;
        try {
            rs = sqlQuery(SQL);
            if (rs.next()) {
                retVal = new ${object.@name}(rs, 0, scope);
            } else {
                throw new ObjectNotFoundException("Could not find ${object.@name} " + id);
            }
        } catch(SQLException e) {
            l.log(SEVERE, "Failed to run query " + SQL, e);
            throw new QueryException("Failed to run query: " + SQL, e);
        } finally {
            close(rs);
        }
        return retVal;
    }

    /**
    * Finds records by custom SQL. Custom queries should be defined in the queries section in the model.
    * @param sql (Mandatory) The SQL statement you wish to use. See also ${object.@name}SQL.
    * @param params (Optional) List of parameters. When omitted it is assumed there are no parameters.
    * @param page (Mandatory) The Page strategy. Allowed scopes are: Scope.Table, Scope.Form, Scope.List, Scope.Lazy.
    * @return Empty or filled list with ${object.@name}
    */
    @NotNull
    public List<${object.@name}> find(@NotNull String sql, @Nullable List<Object> params, @NotNull Page page)
            throws QueryException, CopyException, PagingException {
        List<${object.@name}> retVal = new ArrayList<${object.@name}>();
        String SQL = sql;
        if (params != null) {
            for (Object param : params) {
                SQL = prepare(SQL, param);
            }
        }
        SQL = preparePage(SQL, page);
        ResultSet rs = null;
        try {
            rs = sqlQuery(SQL);
            while (rs.next()) {
                retVal.add(new ${object.@name}(rs, 0, page.getScope()));
            }
        } catch(SQLException e) {
            l.log(SEVERE, "Failed to run query: " + SQL, e);
            throw new QueryException("Failed to run query: " + SQL, e);
        } finally {
            close(rs);
        }

        return retVal;
    }

    @NotNull
    public List<${object.@name}> finder(DefaultFinder finder, Object param, Page page)
            throws QueryException, CopyException, PagingException {
        if (!(page.getScope() == Scope.List || page.getScope() == Scope.Table)) {
            throw new PagingException("Illegal scope (" + page.getScope() + ") for this finder: " + finder);
        }
        String SQL = page.getScope() == Scope.Table ? finder.sqlTable : finder.sqlList;
        if (param instanceof List) {
            return find(page.getScope() == Scope.Table ? finder.sqlTable : finder.sqlList, (List<Object>)param, page);
        } else if (finder != DefaultFinder.FindAll) {
            SQL = prepare(SQL, param);
        }
        return find(SQL, null, page);
    }

    /**
    * Finds All records
    * @param page (Mandatory) The Page strategy. The allowed fetch scopes are: Scope.Table and Scope.List.
    * @return Empty or filled list with ${object.@name}
    */
    @NotNull
    public List<${object.@name}> findAll(@NotNull Page page)
            throws QueryException, CopyException, PagingException {
        return finder(DefaultFinder.FindAll, null, page);
    }

<#list doc["/object/attributes/*/finder"] as finder>
    /**
    * @param page (Mandatory) Allowed: Scope.Table, Scope.List.
    * @return Empty or filled list
    */
    @NotNull
    public List<${object.@name}> ${finder.@name}(@NotNull <#if finder?parent?node_name == "set" || finder?parent?node_name == "object">Long<#else><#if finder?parent?node_name == "enum" && finder?parent.@scope == "local">${object.@name}.</#if>${finder?parent.@className}</#if> finder, @NotNull Page page)
                throws QueryException, CopyException, PagingException {
        if (!(page.getScope() == Scope.List || page.getScope() == Scope.Table)) {
            throw new PagingException("Illegal scope: " + page.getScope());
        }
        List<${object.@name}> retVal = new ArrayList<${object.@name}>();
        String SQL = page.getScope() == Scope.Table ? ${finder.@tableName} : ${finder.@listName};
    <#if finder?parent?node_name == "object" || finder?parent?node_name == "long" || finder?parent?node_name == "integer" || finder?parent?node_name == "date" || finder?parent?node_name == "double">
        SQL = prepare(SQL, finder);
    <#elseif finder?parent?node_name == "string">
        SQL = prepareDirty(SQL, finder);
    <#elseif finder?parent?node_name == "enum">
        SQL = prepare(SQL, finder.name());
    </#if>
        SQL = preparePage(SQL, page);
        ResultSet rs = null;
        try {
            rs = sqlQuery(SQL);
            while (rs.next()) {
                retVal.add(new ${object.@name}(rs, 0, page.getScope()));
            }
        } catch(SQLException e) {
            l.log(SEVERE, "Failed to run query " + SQL, e);
            throw new QueryException("Failed to run query: " + SQL, e);
        } finally {
            close(rs);
        }
        return retVal;
    }
    <#if finder?parent?node_name == "object" && object.trashcan == "true">
    /**
    * Trashes all ${object.@name}s without checking on business rules. This is usually invoked
    * by the related parent which has already checked business rule violations.
    * @param id (Mandatory) Related object id
    */
    public void trashBy${finder?parent.@fieldName?cap_first}Id(@NotNull Long id, boolean undo, boolean asBatch) throws QueryException<#if finder?parent.@parentClass[0]??>, PersistException, CopyException</#if> {
        String SQL = prepare(TRASH_BY_${finder?parent.@fieldName?upper_case}, !undo);
        SQL = prepare(SQL, id);
        execute(SQL);
    }
    </#if>
</#list>

<#if object.lazyScope[0]??>
    /**
    * Fetches all lazy=true attributes. Lazy relationships and objects are not fetched.
    * This means the difference between Table and Form scope is fetched.
    * This method does not exist if there are no lazy fetchable attributes for ${object.@name}.
    * @param id Id of te lazy object
    * @return Null or filled map with lazy objects
    */
    @Nullable
    public Map<String, Object> fetchLazy(@NotNull Long id) throws LazyException {
        Map<String, Object> map = null;
        String SQL = prepare(FETCH_LAZY_BY_ID, id);
        ResultSet rs = null;
        try {
            rs = sqlQuery(SQL);
            if (rs.next()) {
                map = ${object.@name}Util.copyLazy(rs, 0);
            } else {
                throw new LazyException("Could not find ${object.@name} " + SQL);
            }
        } catch(SQLException e) {
            l.log(SEVERE, "Failed to run query " + SQL, e);
            throw new LazyException("Failed to run query: " + SQL, e);
        } catch(CopyException e) {
            l.log(SEVERE, "Failed to copy results " + SQL, e);
            throw new LazyException("Failed to copy results " + SQL, e);
        } finally {
            close(rs);
        }
        return map;
    }
</#if>

<#list doc["/object/attributes/set[@multiplicity='n-to-m']"] as att>
    /**
    * Removes the relationship with ${att.@class} by deleting a record in the ${att.@relationTableName} table.
    * @param ${att.@myName}Id (Mandatory) 'My' side of relationship
    * @param ${att.@theirName}Id (Mandatory) 'Their' side of the relationship
    */
    public void deleteRelationWith${att.@class}(@NotNull Long ${att.@myName}Id, @NotNull Long ${att.@theirName}Id
                                , boolean asBatch) throws QueryException {
        String SQL = prepare(DELETE_${att.@relationTableName?upper_case}_RELATION, ${att.@myName}Id);
        SQL = prepare(SQL, ${att.@theirName}Id);
        execute(SQL);
    }
    <#if object.trashcan == "true">
    /**
    * Moves the relationship with ${att.@class} to the trash can.
    * @param ${att.@myName}Id (Mandatory) 'My' side of relationship
    * @param ${att.@theirName}Id (Mandatory) 'Their' side of the relationship
    * @param undo True to undo it instead of trash
    */
    protected void trashRelationWith${att.@class}(@NotNull Long ${att.@myName}Id
            , @NotNull Long ${att.@theirName}Id, boolean asBatch, boolean undo)
            throws QueryException {
        String SQL = prepare(TRASH_${att.@relationTableName?upper_case}_RELATION, !undo);
        SQL = prepare(SQL, ${att.@myName}Id);
        SQL = prepare(SQL, ${att.@theirName}Id);
        execute(SQL);
    }

    /**
    * Moves all relationships with ${att.@class} to the trash can.
    * @param ${att.@myName}Id (Mandatory) 'My' side of relationship
    * @param undo True will undo all trashed relations
    */
    protected void trashAllRelationsWith${att.@class}(@NotNull  Long ${att.@myName}Id, boolean undo, boolean asBatch)
            throws QueryException {
        String SQL = prepare(TRASH_${att.@relationTableName?upper_case}_RELATION_BY_${att.@myName?upper_case}_ID, !undo);
        SQL = prepare(SQL, ${att.@myName}Id);
        execute(SQL);
    }
    </#if>

    /**
    * Creates a relationship with {att.@class} by creating a record in the ${att.@relationTableName} table.
    * @param ${att.@myName}Id (Mandatory) 'My' side of relationship
    * @param ${att.@theirName}Id (Mandatory) 'Their' side of the relationship
<#if att.@sorted == "both" || att.@sorted == "them">    * @param sortOrder Position in the sorted relation</#if>
    */
    public void connectTo${att.@class}(@NotNull Long ${att.@myName}Id, @NotNull Long ${att.@theirName}Id<#if att.@sorted == "both" || att.@sorted == "them">, int sortOrder</#if>, boolean asBatch)
            throws QueryException, CopyException {
        String SQL = prepare(INSERT_${att.@relationTableName?upper_case}_RELATION, ${att.@myName}Id);
        SQL = prepare(SQL, ${att.@theirName}Id);
    <#if att.@sorted == "both" || att.@sorted == "them">
        SQL = prepare(SQL, sortOrder);
    </#if>
        execute(SQL);
    }

    <#if att.@sorted == "both" || att.@sorted == "them">
    /**
    * Re-orders a sortable relationship with {att.@class} by updating the sortOrder in the ${att.@relationTableName} table.
    * @param ${att.@myName}Id (Mandatory) 'My' side of relationship
    * @param ${att.@theirName}Id (Mandatory) 'Their' side of the relationship
    * @param sortOrder New position in the sorted relation
    */
    public void reconnectTo${att.@class}(@NotNull Long ${att.@myName}Id, @NotNull Long ${att.@theirName}Id, int sortOrder)
            throws QueryException, CopyException {
        String SQL = prepare(UPDATE_${att.@relationTableName?upper_case}_RELATION, sortOrder);
        SQL = prepare(SQL, ${att.@myName}Id);
        SQL = prepare(SQL, ${att.@theirName}Id);
        execute(SQL);
    }
    /**
    * Count the connections in the relationship with {att.@class}.
    * @param ${att.@myName}Id (Mandatory) 'Our' side object id
    * @return 0 or more!
    */
    public int countConnectionsTo${att.@class}(@NotNull Long ${att.@myName}Id)
            throws PersistException, QueryException, CopyException {
        String SQL = prepare(COUNT_${att.@relationTableName?upper_case}_RELATION, ${att.@myName}Id);
        int retVal;
        ResultSet rs = null;
        try {
            rs = sqlQuery(SQL);
            rs.next();
            retVal = rs.getInt(1);
        } catch(SQLException e) {
            l.log(SEVERE, "Failed to run query " + SQL, e);
            throw new QueryException("Failed to run query: " + SQL, e);
        } finally {
            close(rs);
        }
        return retVal;
    }
    </#if>
</#list>

    /**
    * Inserts or updates a collection of ${object.@name}.
    * Collections associated to ${object.@name} are persisted as well.
    * JDBC batch update is used to improve performance.
    * NOTE that this method assumes you already checked if given ${object.plural}
    * have changed and if they did not violate any business rules.
    * @param pojos List of pojo objects you want to persist
    * @return Empty or filled list with violations. Note that (at least the unique
    *            key) is only evaluated in the database.
    */
    @NotNull
    public Set<BusinessRuleViolation> persist(@NotNull Collection<${object.@name}> pojos) throws RedoraException {
        Set<BusinessRuleViolation> retVal = new HashSet<BusinessRuleViolation>();

        boolean transact = !inTransaction;
        if (transact) {
            beginTransaction();
        }
        try {
            for (${object.@name} pojo : pojos)
                retVal.addAll(persist(pojo));
        } catch (RedoraException e) {
            if (transact) {
                transact = false;
                rollback();
            }
            throw e;
        } finally {
            if (transact)
                if (retVal.isEmpty())
                    commit();
                else
                    rollback();
        }

        return retVal;
    }

    /**
    * Inserts or updates ${object.@name}. Collections associated to ${object.@name} are persisted as well.
    * @param pojo (Mandatory) Object you want to persist
    * @return Empty list when OK, else a list of violations.
    */
    @NotNull
    public Set<BusinessRuleViolation> persist(@NotNull ${object.@name} pojo) throws RedoraException {
        if (!pojo.isDirty()) {
            return new HashSet<BusinessRuleViolation>();
        }
        Set<BusinessRuleViolation> br = check(pojo, pojo.isNew ? BusinessRuleViolation.Action.Insert : BusinessRuleViolation.Action.Update);
        if (!br.isEmpty()) {
            return br;
        }
        return persist(pojo, false);
    }

    /**
    * Persists the changes of ${object.@name} to the database and also any changed related children.
    * Business rules are not checked, that is why this method is protected. However, unique key
    * constrains are checked in the database, so this type of business rule is checked.
    * When a unique key constraint is violated, the transaction is rolled back and a BusinessRuleViolation
    * with the violating attribute is returned. If you have set up a transaction yourself (with service.beginTransaction()),
    * you are supposed to rollback (or commit) yourself.<br>
    * The persist function 'knows' the changes in ${object.@name} because of the dirty set in this
    * object. All the setters in ${object.@name} will set the object to dirty when they notice a change
    * in value.<br>
<#if doc["/object/attributes/set"][0]??>
    * Persist will be performed in two actions. First it will persist the ${object.@name},
    * using {@link #soloPersist(${object.@name})}. Then it will persist related children and parents
    * using {@link #familyPersist(${object.@name}, boolean, boolean)}. The whole action is wrapped in a transaction and will be rolled back
    * if somewhere something has failed.
    * @see #familyPersist(${object.@name}, boolean, boolean)
</#if>
    * @see #soloPersist(${object.@name})
    * @param asBatch Set to true if you want to execute the statements outside this method. You need to invoke ps.executeBatch() yourself.
    * @return Empty when OK or filled with a unique key constraint violation
    * @throws PersistException When the action through JDBC / DB has an unexpected problem.
    */
    @NotNull
    protected Set<BusinessRuleViolation> persist(@NotNull ${object.@name} pojo, boolean asBatch)
            throws RedoraException {
        l.log(FINE, "Updating {0}", pojo.getId());
        if (pojo.fetchScope == Scope.List) {
            throw new PersistException("Modification is not allowed, this object is fetched as Scope.List and cannot be persisted. Fetch the object with Table or Form scope instead.");
        }
        final boolean isNew = pojo.isNew;
        Set<BusinessRuleViolation> retVal = null;
<#if doc["/object/attributes/set"][0]??>
        boolean transact = !inTransaction; //only commit/rollback when you are not already in a transaction
        if (transact) {
            beginTransaction();
        }

        try {
</#if>
          retVal = soloPersist(pojo);
<#if doc["/object/attributes/set"][0]??>
            if (retVal.isEmpty()) {
                retVal = familyPersist(pojo, isNew, asBatch);
            }
        } catch (RedoraException e) {
            if (transact) {
                transact = false;
                rollback();
            }
            throw e;
        } finally {
            if (transact) {
                if (retVal != null && retVal.isEmpty()) {
                    commit();
                } else {
                    rollback();
                }
            }
        }
</#if>
        return retVal;
    }

    /**
    * Persists ${object.@name} only. Ignores the children. Does not perform business rule
    * violation checking. So, know what you are doing when using this.
    * @param pojo Object you'd wish persisted
    * @return Empty when OK, or filled with unique key violation.
    */
    @NotNull
    public Set<BusinessRuleViolation> soloPersist(@NotNull ${object.@name} pojo) throws RedoraException {
        Set<BusinessRuleViolation> retVal = new HashSet<BusinessRuleViolation>();

        if (!pojo.dirty.isEmpty() || pojo.isNew) {
            String SQL;
            if (pojo.isNew) {
                pojo.dirty.put(${object.@name}Fields.creationDate, null);
                pojo.creationDate = new Date((new Date().getTime()/1000)*1000); //Strip nanoseconds, they are not persisted in MySQL
                StringBuilder sqlInsert = new StringBuilder("insert into `${object.@name}` (");
                char comma = ' ';
                for (${object.@name}Fields field : pojo.dirty.keySet()) {
                    sqlInsert.append(comma);
                    sqlInsert.append(field.name());
                    comma = ',';
                }
                sqlInsert.append(") values (");
                comma = ' ';
                for (int i = 0; i < pojo.dirty.size(); i++) {
                    sqlInsert.append(comma);
                    sqlInsert.append('?');
                    comma = ',';
                }
                sqlInsert.append(')');
                SQL = sqlInsert.toString();
            } else {
                pojo.dirty.put(${object.@name}Fields.updateDate, pojo.updateDate);
                pojo.updateDate = new Date((new Date().getTime()/1000)*1000); //Strip nanoseconds, they are not persisted in MySQL
                StringBuilder sqlUpdate = new StringBuilder("update `${object.@name}` set ");
                char comma = ' ';
                for (${object.@name}Fields field : pojo.dirty.keySet()) {
                    sqlUpdate.append(comma);
                    sqlUpdate.append(field.name()).append("=?");
                    comma = ',';
                }
                sqlUpdate.append(" where id=?");
                SQL = sqlUpdate.toString();
            }

<#if stream == "true">
            boolean usePreparedStatement = false;
            //Determine if there is a streaming parameter. If yes, use PreparedStatement.
            for (${object.@name}Fields field : pojo.dirty.keySet()) {
    <#list doc["/object/attributes/*"] as att>
        <#if att?node_name == "html">
                if (field == ${object.@name}Fields.${att.@fieldName}) {
                    usePreparedStatement = true;
                    break;
                }
        <#elseif att?node_name == "string" && att.@maxlength[0]?number &gt; 65000>
                if (field == ${object.@name}Fields.${att.@fieldName}) {
                    usePreparedStatement = true;
                    break;
                }
        </#if>
    </#list>
            }
</#if>
<#if stream == "true">
            PreparedStatement ps = null;
            int position = 1;
            if (usePreparedStatement) {
                try {
                    ps = st.con.con.prepareStatement(SQL, java.sql.Statement.RETURN_GENERATED_KEYS);
                    for (${object.@name}Fields field : pojo.dirty.keySet()) {
                        switch (field) {
                            case updateDate:
                                ps.setTimestamp(position++, new Timestamp(pojo.updateDate.getTime()));
                                break;
                            case creationDate:
                                ps.setTimestamp(position++, new Timestamp(pojo.creationDate.getTime()));
                                break;
    <#list object.attributes?children as att>
        <#if att?node_name != "set" && att?node_name != "object" && att?node_type == "element">
                            case ${att.@fieldName}:
            <#if att.@notnull == "false">
                                if (pojo.${att.@fieldName} == null) {
                                    ps.setNull(position++, ${object.@name}Fields.${att.@fieldName}.sqlType);
                                } else {
            </#if>
            <#if att?node_name="boolean">
                                    ps.setBoolean(position++, pojo.${att.@fieldName}.booleanValue());
            <#elseif att?node_name == "date">
            						ps.setDate(position++, new java.sql.Date(pojo.${att.@fieldName}.getTime()));
            <#elseif att?node_name == "datetime">
                                    ps.setTimestamp(position++, new Timestamp(pojo.${att.@fieldName}.getTime()));
            <#elseif att?node_name == "enum">
                                    ps.setString(position++, pojo.${att.@fieldName}.name());
            <#elseif att?node_name == "integer">
                                    ps.setInt(position++, pojo.${att.@fieldName});
            <#elseif att?node_name == "long">
                                    ps.setLong(position++, pojo.${att.@fieldName});
            <#elseif att?node_name == "double">
                                    ps.setDouble(position++, pojo.${att.@fieldName});
            <#elseif att?node_name == "string" || att?node_name == "html">
                <#if att?node_name == "html" || att.@maxlength[0]?number &gt; 65000>
                                    ps.setBinaryStream(position++, org.apache.commons.io.IOUtils.toInputStream(pojo.${att.@fieldName},"UTF-8"));
                <#else>
                                    ps.setString(position++, pojo.${att.@fieldName});
                </#if>
            <#else>
            ERROR undefined type ${att?node_name}
            </#if>
            <#if att.@notnull == "false">
                                }
            </#if>
                                break;
        </#if>
    </#list>
                        } //end switch
                    }
                } catch (SQLException e) {
                    l.log(SEVERE, "Did not set field at position: " + position + " - " + SQL, e);
                    try {if (ps != null) ps.close();} catch (SQLException esql) {}
                    throw new PersistException("Did not set field at position: " + position + " - " + SQL, e);
                } catch (IOException e) {
                    l.log(SEVERE, "Can't stream field at position: " + position + " - " + SQL, e);
                    try {if (ps != null) ps.close();} catch (SQLException esql) {}
                    throw new PersistException("Can't stream field at position: " + position + " - " + SQL, e);
                }
            } else {
</#if>
                for (${object.@name}Fields field : pojo.dirty.keySet()) {
                    switch (field) {
                        case updateDate:
                            SQL = prepareTime(SQL, pojo.updateDate);
                            break;
                        case creationDate:
                            SQL = prepareTime(SQL, pojo.creationDate);
                            break;
<#list object.attributes?children as att>
    <#if att?node_name != "set" && att?node_name != "object" && att?node_type == "element" && (att?node_name != "string" || att.@maxlength[0]?number &lt; 65001)>
                        case ${att.@fieldName}:
        <#if att.@notnull == "false">
                            if (pojo.${att.@fieldName} == null) {
                                SQL = prepareNull(SQL);
                            } else {
        </#if>
        <#if att?node_name="boolean" || att?node_name == "integer" || att?node_name == "long" || att?node_name == "date" || att?node_name == "double">
                                SQL = prepare(SQL, pojo.${att.@fieldName});
        <#elseif att?node_name == "datetime">
                                SQL = prepareTime(SQL, pojo.${att.@fieldName});
        <#elseif att?node_name == "enum">
                                SQL = prepare(SQL, pojo.${att.@fieldName}.name());
        <#elseif att?node_name == "string">
                                SQL = prepareDirty(SQL, pojo.${att.@fieldName});
        </#if>
        <#if att.@notnull == "false">
                            }
        </#if>
                            break;
    </#if>
</#list>
                    } //switch
                }
<#if stream == "true">
            }
</#if>
            ResultSet rs = null;
            try {
<#if stream == "true">
                if (usePreparedStatement) {
                    if (!pojo.isNew) {
                        ps.setLong(position, pojo.getId()); //sets where id = ?
                    }
                    ps.execute();
                    if (ps.getWarnings() != null) {
                        throw new PersistException("${object.@name} was not persisted " + ps.getWarnings().getMessage());
                    }
                    if (pojo.isNew) {
                        rs = ps.getGeneratedKeys();
                        rs.next();
                        pojo.id = rs.getLong(1);
                    }
                } else {
</#if>
                    if (pojo.isNew) {
                        st.st.execute(SQL, java.sql.Statement.RETURN_GENERATED_KEYS);
                    if (st.st.getWarnings() != null) {
                        throw new PersistException("${object.@name} was not persisted " + st.st.getWarnings().getMessage());
                    }
                        rs = st.st.getGeneratedKeys();
                        rs.next();
                        pojo.id = rs.getLong(1);
                    } else {
                        SQL = prepare(SQL, pojo.getId());
                        execute(SQL);
                    }
<#if stream == "true">
                }
</#if>
                pojo.dirty.clear();
                pojo.isNew = false;
            } catch (MySQLIntegrityConstraintViolationException e) {
                int start = e.toString().indexOf("for key '");
                if (start > 0) {
                    String indexName = e.toString().substring(start + 9);
                    indexName = indexName.substring(0, indexName.indexOf("'"));
                    retVal.add(new BusinessRuleViolation(
                            pojo, ${object.@name}Fields.valueOf(uniqueKeyAttribute("${object.@name}", indexName)), BusinessRuleViolation.StandardRule.UniqueKey.ruleId, pojo.getId() == null?BusinessRuleViolation.Action.Insert:BusinessRuleViolation.Action.Update)
                    );
                } else {
                    retVal.add(new BusinessRuleViolation(
                            pojo, null, BusinessRuleViolation.StandardRule.UniqueKey.ruleId, pojo.getId() == null?BusinessRuleViolation.Action.Insert:BusinessRuleViolation.Action.Update)
                    );
                }
            } catch(SQLException e) {
                l.log(SEVERE, "Failed to perform persist: " + SQL, e);
                throw new PersistException("Failed to perform persist: " + SQL, e);
            } finally {
                close(rs);
<#if stream == "true">
                try {if (ps != null) ps.close();} catch (SQLException esql) {}
</#if>
            }
        }
        return retVal;
    }

<#if doc["/object/attributes/set"][0]??>
    /**
    * Goes through dirty children and parents, and persists them. Use only when
    * you know what you are doing.
    * @param pojo (Mandatory) Object from whom you wish to save the children
    * @return Empty when OK, or filled with unique key constraints violations
    */
    @NotNull
    public Set<BusinessRuleViolation> familyPersist(@NotNull ${object.@name} pojo, boolean wasNew, boolean asBatch)
                throws RedoraException {
        Set<BusinessRuleViolation> retVal = new HashSet<BusinessRuleViolation>();
    <#list object.attributes.set as att>
    // ${att.@plural?cap_first} (${att.@multiplicity}) actions, sorted: ${att.@sorted}
        <#if att.@multiplicity == "n-to-m">
        if (pojo.${att.@fieldName}IsRetrieved()) {
            if (!wasNew) {
                for (${att.@class} delete : pojo.get${att.@plural?cap_first}().getRemovedObjects()) {
                    deleteRelationWith${att.@class}(pojo.getId(), delete.getId(), asBatch);
                }
            }
            <#if att.@sorted == "both" || att.@sorted == "them">
            int sort = 0;
            </#if>
            for (${att.@class} list : pojo.get${att.@fieldName?cap_first}()) {
                if (list.isNew || list.isDirty()) {
                    retVal.addAll(<#if att.@class != object.@name>new ${att.@class}Service(this).</#if>persist(list, asBatch));
                }
                if (pojo.get${att.@fieldName?cap_first}().getAddedObjects().contains(list)) {
            <#if att.@sorted == "both" || att.@sorted == "them">
                    connectTo${att.@class}(pojo.getId(), list.getId(), sort, asBatch);
            <#else>
                    connectTo${att.@class}(pojo.getId(), list.getId(), asBatch);
            </#if>
            <#if att.@sorted == "both" || att.@sorted == "them">
                } else if (pojo.get${att.@fieldName?cap_first}().hasShuffled()) {
                    reconnectTo${att.@class}(pojo.getId(), list.getId(), sort);
            </#if>
                }
            <#if att.@sorted == "both" || att.@sorted == "them">
                sort++;
            </#if>
            }
            pojo.get${att.@plural?cap_first}().reset();
        }
        <#else>
            <#assign finderName = att.@myName>
            <#if att.@class == object.@name>
            //pigs ear
                <#assign finderName = att.@theirName>
            </#if>
        if (pojo.${att.@fieldName}IsRetrieved()) {
            if (!wasNew && !pojo.get${att.@fieldName?cap_first}().getRemovedObjects().isEmpty()) {
                for (${att.@class} delete : pojo.get${att.@plural?cap_first}().getRemovedObjects()) {
                    <#if att.@class != object.@name>new ${att.@class}Service(this).</#if>delete(delete);
                }
            }
            if (pojo.get${att.@plural?cap_first}().isDirty(new HashSet<Persistable>())) {
                List<${att.@class}> update${att.@plural?cap_first} = new ArrayList<${att.@class}>();
                for (${att.@class} child : pojo.get${att.@plural?cap_first}()) {
                    if (child.isDirty()) {
                        update${att.@plural?cap_first}.add(child);
                        child.set${finderName?cap_first}(pojo);
                    }
                }
                for (${att.@class} child : pojo.get${att.@plural?cap_first}().getAddedObjects()) {
                    if (!child.isDirty()) { //Dirty is already added before
                        update${att.@plural?cap_first}.add(child);
                        child.set${finderName?cap_first}(pojo);
                    }
                }
                if (!update${att.@plural?cap_first}.isEmpty()) {
                    retVal.addAll(<#if att.@class != object.@name>new ${att.@class}Service(this).</#if>persist(update${att.@plural?cap_first}));
                }
                pojo.get${att.@plural?cap_first}().reset();
            }
        }
        </#if>
    </#list>
        return retVal;
    }
</#if>

    /**
    * Deletes ${object.@name}.
    * Collections associated to ${object.@name} are deleted as well when cascade delete is indicated.
    * @param pojo (Mandatory) ${object.@name} you want to delete.
    * @return Empty when OK, or filled set with violated business rules
    * @throws ObjectNotFoundException when ${object.@name} has no id (was never persisted).
    */
    @NotNull
    public Set<BusinessRuleViolation> delete(@NotNull ${object.@name} pojo) throws RedoraException {
        if (pojo.getId() == null) {
            throw new ObjectNotFoundException("You are trying to delete a ${object.@name} that is not in the database");
        }
        Set<BusinessRuleViolation> br = check(pojo, BusinessRuleViolation.Action.Delete);
        if (br.isEmpty()) {
            delete(pojo, false);
        }

        return br;
    }

    /** Deletes ${object.@name} without checking on business rule violations. */
    protected void delete(${object.@name} pojo, boolean asBatch) throws QueryException {
        l.log(FINE, "Deleting {0}", pojo.getId());

        String SQL = prepare(DELETE, pojo.getId());
        execute(SQL);
    }

<#if object.trashcan == "true">
    /**
    * Finds trashed ${object.@name}s.
    * @param ${object.@name?uncap_first}Id (Optional) if set only a specific trashed object is retrieved.
    * @return Empty or filled list with results
    */
    @NotNull
    public List<${object.@name}> findTrash(@Nullable Long ${object.@name?uncap_first}Id)
            throws QueryException, CopyException {
        List<${object.@name}> retVal = new ArrayList<${object.@name}>();

        String SQL = ${object.@name?uncap_first}Id == null ? FIND_TRASH : FIND_TRASH_BY_ID;
        if (${object.@name?uncap_first}Id != null) {
            SQL = prepare(SQL, ${object.@name?uncap_first}Id);
        }
        ResultSet rs = null;
        try {
            rs = sqlQuery(SQL);
            while (rs.next()) {
                retVal.add(new ${object.@name}(rs, 0, ${object.@name?uncap_first}Id == null ? Scope.Table : Scope.Form));
            }
        } catch(SQLException e) {
            l.log(SEVERE, "Failed to find trash " + SQL, e);
            throw new QueryException("Failed to find trash " + SQL, e);
        } finally {
            close(rs);
        }

        return retVal;
    }

    /**
    * Trashes ${object.@name}.
    * Collections associated to ${object.@name} are trashed as well when cascade delete is indicated.
    * @param undoHash (Optional) When provided, ${object.@name} will be tashed with given hash, otherwise a new hash will be calculated and returned.
    * @param pojo (Mandatory) The ${object.@name} you want to trash.
    * @return Empty when OK, or set with violated business rules.
    * @throws ObjectNotFoundException When ${object.@name} was not persisted.
    */
    @NotNull
    public Set<BusinessRuleViolation> trash(@Nullable String undoHash, @NotNull ${object.@name} pojo) throws RedoraException {
        if (pojo.getId() == null) {
            throw new ObjectNotFoundException("You are trying to delete a ${object.@name} that is not in the database");
        }
        Set<BusinessRuleViolation> br = check(pojo, BusinessRuleViolation.Action.Delete);
        if (br.isEmpty()) {
            TrashService.trash(undoHash, pojo.getId(), "${object.@name}");
            trash(pojo, false, false);
        }

        return br;
    }

    /**
    * Restores a trashed ${object.@name} and also restored possible trashed children.
    * @param ${object.@name?uncap_first}Id (Mandatory) Id of to be retrieved object
    * @throws QueryException When ${object.@name} was not found (or was untrashed before).
    */
    public void undo(Long ${object.@name?uncap_first}Id) throws RedoraException {
        List<${object.@name}> trash = findTrash(${object.@name?uncap_first}Id);

        if (trash.isEmpty()) {
            throw new QueryException("Could not find trashed ${object.@name}, maybe it was already undeleted.");
        }
        trash(trash.get(0), false, true);
        TrashService.deleteTrash(trash.get(0).getId(), "${object.@name}");
    }

    /** Trashes ${object.@name} without checking on business rule violations. */
    private void trash(${object.@name} pojo, boolean asBatch, boolean undo) throws RedoraException {
        l.log(FINE, "Trashing {0}", pojo.getId());

        String SQL = prepare(TRASH, !undo);
        SQL = prepare(SQL, pojo.getId());
        execute(SQL);
    <#list object.attributes.set as att>
        <#if att.@multiplicity == "n-to-m">
        if (undo || !pojo.get${att.@plural?cap_first}().isEmpty()) {
            //trashAllRelationsWith${att.@class}(pojo.getId(), undo, asBatch);
        }
        <#elseif att.@cascade == "true">
            <#assign finderName = att.@myName>
            <#if att.@class == object.@name>
                <#assign finderName = att.@theirName>
            </#if>
        if (undo || !pojo.get${att.@plural?cap_first}().isEmpty()) {
            <#if att.@class != object.@name>new ${att.@class}Service(this).</#if>trashBy${finderName?cap_first}Id(pojo.getId(), undo, asBatch);
        }
        </#if>
    </#list>
    }
</#if>

    /**
    * Simple test method. It will dump the results of findAll to System.out
    * @param args Unused
    * @throws RedoraException Passing on
    */
    public static void main(String[] args) throws RedoraException {
        ${object.@name}Service _${object.@name?uncap_first}Service = ServiceFactory.${object.@name?uncap_first}Service();
        for (${object.@name} testRecord : _${object.@name?uncap_first}Service.findAll(new Page(Scope.Table, Mode.Page, 100))) {
<#list object.tableScope?children as att>
    <#if att?node_name != "object" && att?node_type == "element">
            if (testRecord.${att.@fieldName} != null) {
                System.out.print(testRecord.${att.@fieldName});
            } else {
                System.out.print("NULL");
            }
            System.out.print("-");
    </#if>
</#list>
            System.out.println();
        }
        ServiceFactory.close(_${object.@name?uncap_first}Service);
    }
}
